package com.xuan.yubi.service.impl;


import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuan.yubi.bizmq.BiMessageProducer;
import com.xuan.yubi.common.ErrorCode;
import com.xuan.yubi.common.ResultUtils;
import com.xuan.yubi.constant.ChartStatusConstant;
import com.xuan.yubi.constant.CommonConstant;
import com.xuan.yubi.exception.BusinessException;
import com.xuan.yubi.exception.ThrowUtils;
import com.xuan.yubi.manager.RedisLimiterManager;
import com.xuan.yubi.manager.XunFeiAiManager;
import com.xuan.yubi.model.dto.chart.ChartQueryRequest;
import com.xuan.yubi.model.dto.chart.GenChartByAiRequest;
import com.xuan.yubi.model.entity.Chart;
import com.xuan.yubi.model.entity.User;
import com.xuan.yubi.model.vo.BiResponse;
import com.xuan.yubi.service.ChartService;
import com.xuan.yubi.mapper.ChartMapper;
import com.xuan.yubi.utils.EasyExcelUtils;
import io.github.briqt.spark4j.exception.SparkException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.swing.*;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author 清凉茶
 * @description 针对表【chart(图表表)】的数据库操作Service实现
 * @createDate 2024-05-30 14:27:09
 */
@Service
public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
        implements ChartService {

    @Autowired
    private ChartMapper chartMapper;

    @Autowired
    private XunFeiAiManager xunFeiAiManager;

    @Resource
    private RedisLimiterManager limiterManager;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private BiMessageProducer biMessageProducer;


    @Override
    public void validChart(Chart chart, boolean add) {
        String name = chart.getChartName();
        String goal = chart.getGoal();
        String chartData = chart.getChartData();
        String chartType = chart.getChartType();
        if (StringUtils.isNotBlank(name) && name.length() > 128) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "图表名称过长");
        }
        if (StringUtils.isNotBlank(chartType) && chartType.length() > 128) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "图表类型名称过长");
        }
        ThrowUtils.throwIf(StringUtils.isAnyBlank(goal, chartData), ErrorCode.PARAMS_ERROR, "goal或chartData为空");
    }

    @Override
    public Wrapper<Chart> getQueryWrapper(ChartQueryRequest chartQueryRequest) {
        Long id = chartQueryRequest.getId();
        Long userId = chartQueryRequest.getUserId();
        String searchContent = chartQueryRequest.getChartName();
//        Long userId = chartQueryRequest.getUserId();
//        String goal = chartQueryRequest.getGoal();
//        String chartType = chartQueryRequest.getChartType();
        String sortField = chartQueryRequest.getSortField();
        String sortOrder = chartQueryRequest.getSortOrder();

        QueryWrapper<Chart> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(id != null && id > 0, "id", id);
        queryWrapper.eq(userId != null && userId > 0, "userId", userId);
        if (StringUtils.isNotBlank(searchContent)) {
            queryWrapper.or().like("chartName", searchContent)
                            .or().eq("chartType", searchContent);
        }
        queryWrapper.orderBy(StringUtils.isNotBlank(sortField), CommonConstant.SORT_ORDER_ASC.equals(sortOrder), sortField);
        return queryWrapper;
    }

    @Override
    public boolean updateStatusByParseError(Long chartId, Long userId, boolean isAdmin) {
        ThrowUtils.throwIf(chartId == null, ErrorCode.PARAMS_ERROR);
        // 判断表是否存在以及是否有权限
        isExistAndIsAuth(userId, isAdmin, chartId);

        Chart update = new Chart();
        update.setChartStatus("failed");
        update.setId(chartId);
        return chartMapper.updateById(update) > 0;
    }



    @Override
    public Chart getChartVoById(long chartId, Long userId, boolean isAdmin) {
        // 判断表是否存在以及是否有权限
        return isExistAndIsAuth(userId, isAdmin, chartId);

    }

    @Override
    public boolean deleteChart(long chartId, Long userId, boolean isAdmin) {
        isExistAndIsAuth(userId, isAdmin, chartId);

        return chartMapper.deleteById(chartId) > 0;
    }

    @Override
    public Page<Chart> listMyChartVOByPage(ChartQueryRequest chartQueryRequest) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 30, ErrorCode.PARAMS_ERROR);
        return chartMapper.selectPage(new Page<>(current, size),
                getQueryWrapper(chartQueryRequest));
    }

    @Override
    public BiResponse genChartByAiSync(MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest, Long userId) {
        // 1. 检查 文件和请求参数 的合法性并返回文件类型
        ExcelTypeEnum targetExcelTypeEnum = checkFileIsValid(multipartFile, genChartByAiRequest);

        // 2. 检查是否超出调用频率限制(失败会抛异常)
        limiterManager.doRateLimit("genChartByAi_" + userId);

        // 3. 将文件转换为 csv 格式(节省token,以便向Al提问传输更多数据)
        String csvData = EasyExcelUtils.excelToCsv(multipartFile, targetExcelTypeEnum);
        // 4. 将用户需求组装成预设的 AI 提问方式
        String question = assembleToQuestion(genChartByAiRequest, csvData);
        System.out.println(question);
        // 5. 调用 AI 能力获取提问结果
        String result = null;
        try {
            result = xunFeiAiManager.doChat(question);
        } catch (SparkException e) {
            return handleCallAiError("系统繁忙,请前往图表中心重试", "星火大模型调用出错", genChartByAiRequest, csvData, userId);
        }
        System.out.println(result);
        String[] split = result.split("【【【【【");
        if (split.length < 3) {
            return handleCallAiError("AI 生成错误,请前往图表中心重试", "分割字符串长度小于 3 ", genChartByAiRequest, csvData, userId);
        }

        // 对生成的 option 对象进行解析校验
        String genChart = split[1].trim();
        String genResult = split[2].trim();
        try {
            JSONObject jsonObject = JSONUtil.parseObj(genChart);
            genChart = jsonObject.toString();
        } catch (Exception e) {
            return handleCallAiError("AI 生成错误,请前往图表中心重试", "option 对象解析出错", genChartByAiRequest, csvData, userId);
        }

        // 6. 将数据保存到数据库
        String chartName = genChartByAiRequest.getChartName();
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();

        Chart chart = new Chart();
        chart.setChartStatus(ChartStatusConstant.SUCCEED);
        chart.setChartName(chartName);
        chart.setUserId(userId);
        chart.setGoal(goal);
        chart.setChartData(csvData);
        chart.setChartType(chartType);
        chart.setGenChart(genChart);
        chart.setGenResult(genResult);
        boolean save = this.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "图表信息保存失败");

        // 7. 返回结果
        BiResponse biResponse = new BiResponse();
        biResponse.setChartId(chart.getId());
        biResponse.setGenChart(genChart);
        biResponse.setGenResult(genResult);
        return biResponse;
    }



    @Override
    public BiResponse genChartByAiAsync(MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest, Long userId) {
        // 1. 检查 文件和请求参数 的合法性并返回文件类型
        ExcelTypeEnum targetExcelTypeEnum = checkFileIsValid(multipartFile, genChartByAiRequest);

        // 2. 检查是否超出调用频率限制(失败会抛异常)
        limiterManager.doRateLimit("genChartByAi_" + userId);

        // 3. 将文件转换为 csv 格式(节省token,以便向Al提问传输更多数据)
        String csvData = EasyExcelUtils.excelToCsv(multipartFile, targetExcelTypeEnum);
        // 4. 将用户需求组装成预设的 AI 提问方式
        String question = assembleToQuestion(genChartByAiRequest, csvData);


        // 4. 将分析需求和数据保存到数据库
        String chartName = genChartByAiRequest.getChartName();
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();
        Chart chart = new Chart();
        chart.setChartName(chartName);
        chart.setUserId(userId);
        chart.setGoal(goal);
        chart.setChartData(csvData);
        chart.setChartType(chartType);
        boolean save = this.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "图表信息保存失败");


        // 6. 将 AI 分析任务提交到线程池中
        CompletableFuture.runAsync(() -> {
            // 1) 将任务执行状态修改为"running"
            Chart updateChart = new Chart();
            updateChart.setId(chart.getId());
            updateChart.setChartStatus(ChartStatusConstant.RUNNING);
            boolean b = this.updateById(updateChart);
            if (!b) {
                handleUpdateChartError(chart.getId(), "更新图表执行中状态失败");
            }
            // 2)调用 AI 分析服务
            String result = xunFeiAiManager.doChat(question);
            String[] split = result.split("【【【【【");
            if (split.length < 2) {
                handleUpdateChartError(chart.getId(), "AI 生成错误");
            }
            String genChart = split[1].trim();
            String genResult = split[2].trim();
            updateChart.setChartStatus(ChartStatusConstant.SUCCEED);
            updateChart.setGenChart(genChart);
            updateChart.setGenResult(genResult);
            b = this.updateById(updateChart);
            if (!b) {
                handleUpdateChartError(chart.getId(), "更新图表成功状态失败");
            }
        }, threadPoolExecutor);

        // 7. 提前返回结果
        BiResponse biResponse = new BiResponse();
        biResponse.setChartId(chart.getId());
        return biResponse;
    }

    @Override
    public BiResponse genChartByAiAsyncMq(MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest, Long userId) {
        // 1. 检查 文件和请求参数 的合法性并返回文件类型
        ExcelTypeEnum targetExcelTypeEnum = checkFileIsValid(multipartFile, genChartByAiRequest);
        // 2. 检查是否超出调用频率限制(失败会抛异常)
        limiterManager.doRateLimit("genChartByAi_" + userId);
        // 3. 将文件转换为 csv 格式(节省token,以便向Al提问传输更多数据)
        String csvData = EasyExcelUtils.excelToCsv(multipartFile, targetExcelTypeEnum);

        // 4. 将分析需求和数据保存到数据库
        String chartName = genChartByAiRequest.getChartName();
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();

        Chart chart = new Chart();
        chart.setChartName(chartName);
        chart.setUserId(userId);
        chart.setGoal(goal);
        chart.setChartData(csvData);
        chart.setChartType(chartType);
        boolean save = this.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "图表信息保存失败");

        // 5. 生产者发送消息到队列
        biMessageProducer.sendMessage(String.valueOf(chart.getId()));

        // 6. 提前返回结果
        BiResponse biResponse = new BiResponse();
        biResponse.setChartId(chart.getId());
        return biResponse;
    }

    @Override
    public boolean retryGen(Long chartId, Long userId) {
        ThrowUtils.throwIf(chartId == null || userId == null, ErrorCode.PARAMS_ERROR);
        // 1. 查询图表信息是否存在
        Chart chart = chartMapper.selectById(chartId);
        if (chart == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "图表不存在");
        }
        if (!Objects.equals(chart.getUserId(), userId)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限");
        }
        // 2. 将图表 id 放入消息队列,等待重新生成
        biMessageProducer.sendMessage(String.valueOf(chartId));
        // 3. 将数据库中图表的状态设置 等待生成
        Chart update = new Chart();
        update.setId(chartId);
        update.setChartStatus(ChartStatusConstant.WAIT);
        int result = chartMapper.updateById(update);
        return result > 0;
    }


    private BiResponse handleCallAiError(String respErrMsg, String realErrMsg, GenChartByAiRequest genChartByAiRequest, String csvData, long userId) {
        log.error("调用 AI 出错,原因: " + realErrMsg);

        // 1. 将用户数据保存到数据库
        Chart chart = new Chart();
        BeanUtils.copyProperties(genChartByAiRequest, chart);
        chart.setChartData(csvData);
        chart.setUserId(userId);
        chart.setChartStatus(ChartStatusConstant.FAIL);
        chart.setExeMessage("AI 生成出错");
        chartMapper.insert(chart);

        // 2. 构造返回结果
        BiResponse biResponse = new BiResponse();
        biResponse.setChartId(chart.getId());
        biResponse.setGenChart("{}");
        biResponse.setGenResult(respErrMsg);
        return biResponse;
    }


    /**
     *  检查图表是否存在，以及用户是否有查看权限
     * @param userId
     * @param isAdmin
     * @param chartId
     * @return
     */
    private Chart isExistAndIsAuth(Long userId, boolean isAdmin, Long chartId) {
        Chart chart = chartMapper.selectById(chartId);
        if (chart == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "图表不存在");
        }
        if (!Objects.equals(userId, chart.getUserId()) && !isAdmin) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限");
        }
        return chart;
    }


    /**
     *  检查文件及传入参数的合法性
     * @param multipartFile
     * @param genChartByAiRequest
     * @return
     */
    private ExcelTypeEnum checkFileIsValid(MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest) {
        String chartName = genChartByAiRequest.getChartName();
        // 1. 参数检查
        // 1) 检查必传的参数(目标和文件)
        String goal = genChartByAiRequest.getGoal();
        boolean fileIsEmpty = multipartFile.isEmpty();
        ThrowUtils.throwIf(StringUtils.isAnyBlank(goal, chartName) || fileIsEmpty, ErrorCode.PARAMS_ERROR, "分析目标或文件为空");
        // 2) 检查参数的长度是否合法
        ThrowUtils.throwIf(chartName.length() > 128, ErrorCode.PARAMS_ERROR, "图表名称过长");
        // 3) 检查文件类型是否合法(文件后缀,但并不安全,因为可以任意修改)
        String originalFilename = multipartFile.getOriginalFilename();
        String suffix = "." + FileUtil.getSuffix(originalFilename);
        ExcelTypeEnum targetExcelTypeEnum = null;
        for (ExcelTypeEnum excelTypeEnum : ExcelTypeEnum.values()) {
            if (excelTypeEnum.getValue().equals(suffix)) {
                targetExcelTypeEnum = excelTypeEnum;
                break;
            }
        }
        if (targetExcelTypeEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型非法");
        }
        // 4) 检查文件大小(限制最大上传大小为 1M)
        long size = multipartFile.getSize();
        final long ONE_MB = 1024 * 1024L;
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件过大");
        return targetExcelTypeEnum;
    }


    /**
     *  拼接用户分析需求和期望图表类型(若存在)
     * @param genChartByAiRequest
     * @param csvData
     * @return
     */
    private String assembleToQuestion(GenChartByAiRequest genChartByAiRequest, String csvData) {
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();
        StringBuilder userInput = new StringBuilder();

        userInput.append("分析需求:").append('\n');
        String userGoal = goal;
        if (StringUtils.isNotBlank(chartType)) {
            userGoal += ", 请使用" + chartType;
        }
        userInput.append(userGoal).append('\n');
        // 2) 拼接压缩后的原始数据
        userInput.append("原始数据:").append("\n");
        userInput.append(csvData);
        return userInput.toString();
    }


    /**
     * 保存图表生成过程中的错误信息
     *
     * @param chartId
     * @param exeMessage
     */
    private void handleUpdateChartError(Long chartId, String exeMessage) {
        Chart updateChart = new Chart();
        updateChart.setId(chartId);
        updateChart.setChartStatus(ChartStatusConstant.FAIL);
        updateChart.setExeMessage(exeMessage);
        this.updateById(updateChart);
        throw new BusinessException(ErrorCode.SYSTEM_ERROR, exeMessage);
    }


}




